#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>

#include "../getopt_helper.h"

static struct option const LONG_OPTIONS[] = {
	{ "logical", no_argument, NULL, 'L' },
	{ "physical", no_argument, NULL, 'P' },
	GETOPT_END_DECL
};

static const char usage_str[] =
    "Usage: %s [OPTION]...\n"
    "Print the full filename of the current working directory.\n"
    "\n"
    "  -L, --logical   use PWD from environment, even if it contains "
    "symlinks\n"
    "  -P, --physical  avoid all symlinks\n"
    "      --help     display this help and exit\n"
    "      --version  output version information and exit\n"
    "\n"
    "If no option is specified, -P is assumed.\n"
    "\n"
    "NOTE: your shell may have its own version of pwd, which usually "
    "supersedes\n"
    "the version described here.  Please refer to your shell's documentation\n"
    "for details about the options it supports.";

static inline char* logical_pwd(void);

int
main(int argc, char* argv[])

{
	int	  ch;
	char*	  pwd	  = NULL;
	enum flag logical = NO, need_free = NO;

	while ((ch = getopt_long(argc, argv, "LP", LONG_OPTIONS, NULL))
	       != -1) {
		switch (ch) {
		case 'L':
			logical = YES;
			break;

		case 'P':
			logical = NO;
			break;

			GETOPT_END_CASE
		}
	}

	if (*(argv + optind)) warnx("ignoring non-option arguments");

	if (logical) pwd = logical_pwd();

	if (!pwd) {
		if (!(pwd = getcwd(NULL, 0))) err(!0, "getcwd failed");
		need_free = YES;
	}

	puts(pwd);

	if (need_free) free(pwd);
	return 0;
}

static inline char*
logical_pwd(void)
{
	char* pwd;

	struct stat s_pwd, s_dot;

	if (!(pwd = getenv("PWD")) || *pwd != '/') return NULL;

	for (char const* ptr = pwd; *ptr; ++ptr)
		if (ptr[0] == '/' && ptr[1] == '.') switch (ptr[2]) {
			case '.':
				++ptr;
				break;

			case '\0': /* fallthrough */
			case '/':
				return NULL;
			}

	if (stat(pwd, &s_pwd) || stat(".", &s_dot)) return NULL;

	if (s_pwd.st_dev != s_dot.st_dev || s_pwd.st_ino != s_dot.st_ino)
		return NULL;

	return pwd;
}
